﻿--------------------------
-- out-source functions --
--------------------------

function check ( funcname, ... )
	local Error = false
	local arg = { ... }
 
	if ( type ( funcname ) ~= "string" ) then
		error ( "Argument type mismatch at 'Check' ( 'funcname' ). Expected 'string', got '" .. type ( funcname ).. "'.", 2 )
		Error = true
	end
	if ( #arg % 3 > 0 ) then
		error ( "Argument number mismatch at 'Check'. Expected #arg % 3 to be 0, but it is " .. ( #arg % 3 ).. ".", 2 )
		Error = true
	end
 
	for i=1, #arg-2, 3 do
		if ( type ( arg [ i ] ) ~= "string" and type ( arg [ i ] ) ~= "table" ) then
			error ( "Argument type mismatch at 'Check' ( arg #" .. i .. " ). Expected 'string' or 'table', got '" .. type ( arg [ i ] ).. "'.", 2 )
			Error = true
		elseif ( type ( arg [ i + 2 ] ) ~= "string" ) then
			error ( "Argument type mismatch at 'Check' ( arg #" .. ( i + 2 ).. " ). Expected 'string', got '" .. type ( arg [ i + 2 ] ).. "'.", 2 )
			Error = true
		end
 
		if ( type ( arg [ i ] ) == "table" ) then
			local aType = type ( arg [ i + 1 ] )
			for _, pType in next, arg [ i ] do
				if ( aType == pType ) then
					aType = nil
					break
				end
			end
			if ( aType ) then
				error ( "Argument type mismatch at '" .. funcname .. "' ( '" .. arg [ i + 2 ] .. "' ). Expected '" .. table.concat ( arg [ i ] , "' or '" ).. "', got '" .. aType .. "'.", 3 )
				Error = true
			end
		elseif ( type ( arg [ i + 1 ] ) ~= arg [ i ] ) then
			error ( "Argument type mismatch at '" .. funcname .. "' ( '" .. arg [ i + 2 ] .. "' ). Expected '" .. arg [ i ] .. "', got '" .. type ( arg [ i + 1 ] ).. "'.", 3 )
			Error = true
		end
	end
	if Error then
		return false
	else
		return true
	end
end

function callClientFunction ( client, funcname, ... )
	local arg = { ... }
	if ( arg [ 1 ] ) then
		for key, value in next, arg do
			if ( type ( value ) == "number" ) then 
				arg [ key ] = tostring ( value ) 
			end
		end
	end
	-- If the clientside event handler is not in the same resource, replace 'resourceRoot' with the appropriate element
	triggerClientEvent ( client, "onServerCallsClientFunction", resourceRoot, funcname, unpack ( arg or {} ) )
end

--[[function callServerFunction ( funcname, ... )
	local arg = { ... }
	if ( arg [ 1 ] ) then
		for key, value in next, arg do
			arg [ key ] = tonumber ( value ) or value
		end
	end
	loadstring ( "return " .. funcname ) () ( unpack ( arg ) )
end
addEvent ( "onClientCallsServerFunction", true )
addEventHandler ( "onClientCallsServerFunction", resourceRoot , callServerFunction )]]

---------------
-- variables --
---------------

acceptedWeaponModels = { 
	[ 22 ] = "colt 45",
	[ 23 ] = "silenced",
	[ 24 ] = "deagle",
	--[ 25 ] = "shotgun",
	--[ 26 ] = "sawn-off",
	--[ 27 ] = "spaz 12",
	[ 28 ] = "uzi",
	[ 29 ] = "mp5",
	[ 30 ] = "ak-47",
	[ 31 ] = "m4",
	[ 32 ] = "tec-9",
	[ 33 ] = "rifle",
	[ 34 ] = "sniper",
	[ 35 ] = "rocket launcher",
	[ 36 ] = "rocket launcher hs",
	[ 37 ] = "flamethrower",
	[ 38 ] = "minigun",
	[ 39 ] = "satchel",
	[ 41 ] = "spraycan",
	[ 42 ] = "fire extinguisher",
	[ 43 ] = "camera"
}

acceptedWeaponStates = {
	[ 0 ] = "ready",
	[ 1 ] = "firing"
}

--[[ types of data of weapons
- Model <string> - a value from acceptedWeaponModels table
- Owner <player element>
- State <string> - "ready" or "firing"
- Alpha <float> - 0-255
- Position <table: x, y, z <numbers>>
- Rotation <table: rx, ry, rz <numbers>>
- AttachedTo <element>
- AttachedOffsets <table: x, y, z, rx, ry, rz <numbers>>
- todo? Dimension <integer>
- todo? Interior <integer>
- todo? WeaponData <table: [ key ] = value>
]]
 
--------------------
-- dummy handling --
--------------------

weaponDummies = {}

---------------------
-- basic functions --
---------------------

function allPlayers ()
	return getElementsByType ( "player" )
end

--------------------
-- main functions --
--------------------

function createWeapon ( theModel, x, y, z, rx, ry, rz )
	check ( "createWeapon",
		{ "string", "number" }, theModel, "theModel",
		{ "number", "nil" }, x, "posX",
		{ "number", "nil" }, y, "posY",
		{ "number", "nil" }, z, "posZ",
		{ "number", "nil" }, rx, "rotX",
		{ "number", "nil" }, ry, "rotY",
		{ "number", "nil" }, rz, "rotZ" )
	local newModel
	for id, weaponModel in pairs ( acceptedWeaponModels ) do
		if id == theModel then
			newModel = weaponModel
			break
		elseif weaponModel == theModel then
			newModel = weaponModel
			break
		end
	end
	if not newModel then
		error ( "Bad argument at 'createWeapon' ( 'theModel' ).", 3 )
		return false
	end

	local x, y, z, rx, ry, rz = tonumber ( x ) or 0, tonumber ( y ) or 0, tonumber ( z ) or 0, tonumber ( rx ) or 0, tonumber ( ry ) or 0, tonumber ( rz ) or 0
	local weaponID = #weaponDummies + 1
	local weaponDummy = createElement ( "weaponDummy", weaponID )
	weaponDummies [ weaponID ] = weaponDummy
	setElementData ( weaponDummy, "weaponModel", newModel )
	setElementData ( weaponDummy, "weaponState", "ready" )
	setElementData ( weaponDummy, "weaponPosition", { x, y, z } )
	setElementData ( weaponDummy, "weaponRotation", { rx, ry, rz } )
	
	for k, player in ipairs ( allPlayers() ) do
		callClientFunction ( player, "clientCreateWeapon", weaponDummy )
	end
	return weaponDummy
end

function destroyWeapon ( weaponDummy )
	if not check ( "destroyWeapon", "userdata", weaponDummy, "weaponDummy" ) or getElementType ( weaponDummy ) ~= "weaponDummy" then
		return false
	end
	
	local weaponID = getElementID ( weaponDummy )
	destroyElement ( weaponDummy )
	weaponDummies [ weaponID ] = nil
	
	for k, player in ipairs ( allPlayers() ) do
		callClientFunction ( player, "clientDestroyWeapon", weaponID )
	end
	return true
end

function getWeaponPosition ( weaponDummy )
	if not check ( "getWeaponPosition", "userdata", weaponDummy, "weaponDummy" ) or getElementType ( weaponDummy ) ~= "weaponDummy" then
		return false
	end
	
	local positionTable = getElementData ( weaponDummy, "weaponPosition" )
	if positionTable then
		return unpack ( positionTable )
	else
		return false
	end
end

function setWeaponPosition ( weaponDummy, x, y, z )
	if not check ( "setWeaponPosition", "userdata", weaponDummy, "weaponDummy", "number", x, "posX", "number", y, "posY", "number", z, "posZ" ) or getElementType ( weaponDummy ) ~= "weaponDummy" then
		return false
	end
	
	setElementData ( weaponDummy, "weaponPosition", { x, y, z } )
	for k, player in ipairs ( allPlayers () ) do
		callClientFunction ( player, "clientSetWeaponPosition", weaponDummy, x, y, z )
	end
	return true
end

function getWeaponRotation ( weaponDummy )
	if not check ( "getWeaponRotation", "userdata", weaponDummy, "weaponDummy" ) or getElementType ( weaponDummy ) ~= "weaponDummy" then
		return false
	end
	
	local rotationTable = getElementData ( weaponDummy, "weaponRotation" )
	if rotationTable then
		return unpack ( rotationTable )
	else
		return false
	end
end

function setWeaponRotation ( weaponDummy, rx, ry, rz )
	if not check ( "setWeaponRotation", "userdata", weaponDummy, "weaponDummy", "number", x, "posX", "number", y, "posY", "number", z, "posZ" ) or getElementType ( weaponDummy ) ~= "weaponDummy" then
		return false
	end
	
	setElementData ( weaponDummy, "weaponRotation", { rx, ry, rz } )
	for k, player in ipairs ( allPlayers () ) do
		callClientFunction ( player, "clientSetWeaponRotation", weaponDummy, rx, ry, rz )
	end
	return true
end

function getWeaponAlpha ( weaponDummy )
	if not check ( "getWeaponRotation", "userdata", weaponDummy, "weaponDummy" ) or getElementType ( weaponDummy ) ~= "weaponDummy" then
		return false
	end
	
	return getElementData ( weaponDummy, "weaponAlpha" )
end

function setWeaponAlpha ( weaponDummy, newAlpha )
	if not check ( "getWeaponRotation", "userdata", weaponDummy, "weaponDummy", "number", newAlpha, "newAlpha" ) or getElementType ( weaponDummy ) ~= "weaponDummy" then
		return false
	end
	
	if newAlpha < 0 or newAlpha > 255 then
		error ( "Bad argument at 'setWeaponAlpha' ( 'newAlpha' ).", 3 )
		return false
	end
	
	setElementData ( weaponDummy, "weaponAlpha", newAlpha )
	for k, player in ipairs ( allPlayers () ) do
		callClientFunction ( player, "clientSetWeaponAlpha", weaponDummy, newAlpha )
	end
end

function attachWeaponToElement ( weaponDummy, theElement, x, y, z, rx, ry, rz )
	check ( "attachWeaponToElement",
		{ "number", "nil" }, x, "posX", 
		{ "number", "nil" }, y, "posY", 
		{ "number", "nil" }, z, "posZ", 
		{ "number", "nil" }, rx, "rotX", 
		{ "number", "nil" }, ry, "rotY", 
		{ "number", "nil" }, rz, "rotZ" )
	if not check ( "setWeaponRotation", "userdata", weaponDummy, "weaponDummy", "userdata", theElement, "theElement" ) or getElementType ( weaponDummy ) ~= "weaponDummy" then
		return false
	end
	
	local x, y, z, rx, ry, rz = tonumber ( x ) or 0, tonumber ( y ) or 0, tonumber ( z ) or 0, tonumber ( rx ) or 0, tonumber ( ry ) or 0, tonumber ( rz ) or 0
	setElementData ( weaponDummy, "weaponAttachedTo", theElement )
	setElementData ( weaponDummy, "weaponAttachedOffsets", { x, y, z, rx, ry, rz } )
	for k, player in ipairs ( allPlayers () ) do
		callClientFunction ( player, "clientAttachWeaponToElement", weaponDummy, theElement, x, y, z, rx, ry, rz )
	end
	return true
end

function getAttachedElement ( weaponDummy )
	if not check ( "getAttachedElement", "userdata", weaponDummy, "weaponDummy" ) or getElementType ( weaponDummy ) ~= "weaponDummy" then
		return false
	end
	
	return getElementData ( weaponDummy, "weaponAttachedTo" )
end

function getAttachedWeaponOffsets ( weaponDummy )
	if not check ( "getAttachedElement", "userdata", weaponDummy, "weaponDummy" ) or getElementType ( weaponDummy ) ~= "weaponDummy" then
		return false
	end
	
	return unpack ( getElementData ( weaponDummy, "weaponAttachedOffsets" ) or { false } )
end

function setAttachedWeaponOffsets ( weaponDummy, x, y, z, rx, ry, rz )
	if not check ( "setAttachedWeaponOffsets", "userdata", weaponDummy, "weaponDummy","number", x, "posX","number", y, "posY","number", z, "posZ" ) or getElementType ( weaponDummy ) ~= "weaponDummy" then
		return false
	end
	local ox, oy, oz, orx, ory, orz = getAttachedWeaponOffsets ( weaponDummy )
	if rx then
		if not check ( "setAttachedWeaponOffsets", "number", rx, "rotX" ) then
			rx = orx
		end
	end
	if ry then
		if not check ( "setAttachedWeaponOffsets", "number", ry, "rotY" ) then
			ry = ory
		end
	end
	if rz then
		if not check ( "setAttachedWeaponOffsets", "number", rz, "rotZ" ) then
			rz = orz
		end
	end
	
	setElementData ( weaponDummy, "weaponAttachedOffsets", { x, y, z, rx, ry, rz } )
	local weaponAttachedTo = getElementData ( weaponDummy, "weaponAttachedTo" )
	for k, player in ipairs ( allPlayers () ) do
		callClientFunction ( player, "clientAttachWeaponToElement", weaponDummy, weaponAttachedTo, x, y, z, rx, ry, rz )
	end
	return true
end

function detachWeapon ( weaponDummy )
	if not check ( "detachWeapon", "userdata", weaponDummy, "weaponDummy" ) or getElementType ( weaponDummy ) ~= "weaponDummy" then
		return false
	end
	
	local element = getAttachedElement ( weaponDummy )
	if element then
		setElementData ( weaponDummy, "weaponAttachedTo", nil )
		setElementData ( weaponDummy, "weaponAttachedElements", nil )
		
		for k, player in ipairs ( allPlayers () ) do
			callClientFunction ( player, "clientDetachWeapon", weaponDummy )
		end
		return true
	else
		return false
	end
end
--[[ TODO ?
function getWeaponData ( weaponDummy, key )
	--todo?
end

function setWeaponData ( weaponDummy, key, value )
	--todo?
end

function getWeaponDimension ( weaponDummy )
	--todo?
end

function setWeaponDimension ( weaponDummy, theDimension )
	--todo?
end

function getWeaponInterior ( weaponDummy )
	--todo?
end

function setWeaponInterior ( weaponDummy, theInterior )
	--todo?
end
]]
-------------------------------
-- weapon specific functions --
-------------------------------

function fireWeapon ( weaponDummy )
	if not check ( "fireWeapon", "userdata", weaponDummy, "weaponDummy" ) or getElementType ( weaponDummy ) ~= "weaponDummy" then
		return false
	end
	
	setElementData ( weaponDummy, "weaponState", "firing" )
	for k, player in ipairs ( allPlayers () ) do
		callClientFunction ( player, "clientFireWeapon", weaponDummy )
	end
	setElementData ( weaponDummy, "weaponState", "ready" )
	return true
end

function getWeaponModel ( weaponDummy )
	if not check ( "getWeaponModel", "userdata", weaponDummy, "weaponDummy" ) or getElementType ( weaponDummy ) ~= "weaponDummy" then
		return false
	end
	
	return getElementData ( weaponDummy, "weaponModel" )
end

function setWeaponModel ( weaponDummy, theModel )
	if not check ( "setWeaponModel", "userdata", weaponDummy, "weaponDummy" ) or getElementType ( weaponDummy ) ~= "weaponDummy" then
		return false
	end
	
	if getElementData ( weaponDummy, "weaponModel" ) ~= theModel then
		local newModel
		for id, weaponModel in pairs ( acceptedWeaponModels ) do
			if id == theModel then
				newModel = weaponModel
				break
			elseif weaponModel == theModel then
				newModel = weaponModel
				break
			end
		end
		if not newModel then
			error ( "Bad argument at 'setWeaponModel' ( 'theModel' ).", 3 )
			return false
		end
		setElementData ( weaponDummy, "weaponModel", newModel )
		for k, player in ipairs ( allPlayers () ) do
			callClientFunction ( player, "clientSetWeaponModel", weaponDummy, newModel )
		end
		return true
	else
		return false
	end
end

function getWeaponState ( weaponDummy )
	if not check ( "getWeaponState", "userdata", weaponDummy, "weaponDummy" ) or getElementType ( weaponDummy ) ~= "weaponDummy" then
		return false
	end
	
	return getElementData ( weaponDummy, "weaponState" )
end

function setWeaponState ( weaponDummy, theState )
	if not check ( "setWeaponState", "userdata", weaponDummy, "weaponDummy", { "string", "number" }, theState, "theState" ) or getElementType ( weaponDummy ) ~= "weaponDummy" then
		return false
	end
	local newState
	for id, weaponState in pairs ( acceptedWeaponStates ) do
		if id == theState then
			newState = id
			break
		elseif weaponState == theState then
			newState = weaponState
			break
		end
	end
	if not newState then
		error ( "Bad argument at 'createWeapon' ( 'theModel' ).", 3 )
		return false
	end
	
	setElementData ( weaponDummy, "weaponState", newState )
	for k, player in ipairs ( allPlayers () ) do
		callClientFunction ( player, "clientSetWeaponState", weaponDummy, newState )
	end
	return true
end

function getWeaponOwner ( weaponDummy )
	if not check ( "getWeaponOwner", "userdata", weaponDummy, "weaponDummy" ) or getElementType ( weaponDummy ) ~= "weaponDummy" then
		return false
	end
	
	return getElementData ( weaponDummy, "weaponOwner" )
end

function setWeaponOwner ( weaponDummy, theOwner )
	if not check ( "setWeaponOwner", "userdata", weaponDummy, "weaponDummy", { "userdata", "nil" }, theOwner, "theOwner" ) or getElementType ( weaponDummy ) ~= "weaponDummy" or ( isElement ( theOwner ) and getElementType ( theOwner ) ~= "player" ) then
		return false
	end
	
	setElementData ( weaponDummy, "weaponOwner", theOwner )
	for k, player in ipairs ( allPlayers () ) do
		callClientFunction ( player, "clientSetWeaponOwner", weaponDummy, theOwner )
	end
	return true
end

-------------------
-- data updating --
-------------------

function createAllWeapons ()
	setTimer ( callClientFunction, 100, 1, source, "clientCreateAllWeapons" )
end

function removeWeaponOwner ()
	for k, weaponDummy in ipairs ( getElementsByType ( "weaponDummy", source ) ) do
		setWeaponOwner ( weaponDummy, nil )
	end
end

--------------
-- handlers --
--------------

addEventHandler ( "onPlayerJoin", root, createAllWeapons )
addEventHandler ( "onPlayerQuit", root, removeWeaponOwner ) 